// Copyright (c) 2024, the Dart project authors.
// All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:ffi' as ffi;

import 'package:ffi/ffi.dart';
import 'package:meta/meta.dart' show internal;

import 'errors.dart';
import 'napi.dart';
import 'types.dart';
import 'third_party/generated_bindings.dart' hide napi_value;

@internal
extension ProtectedArkReference on ArkReference {
  void setAsReleased() {
    _setAsReleased();
  }

  /// Similar to [pointer].
  ///
  /// Detaches the finalizer so the underlying pointer will not be deleted.
  NapiObjectPtr toPointer() {
    _setAsReleased();
    return _finalizable.pointer;
  }
}

/// A thin wrapper around a pointer that makes it manageable.
/// In HarmonyOS Napi, we don't use Dart's Finalizable interface
/// but instead rely on Napi's own reference management.
@pragma('vm:deeply-immutable')
final class _ArkFinalizable {
  final ffi.Pointer<ffi.Void> pointer;

  _ArkFinalizable(this.pointer);
}

@pragma('vm:deeply-immutable')
abstract final class ArkReference {
  final _ArkFinalizable _finalizable;

  ArkReference(this._finalizable);

  /// The underlying Napi reference.
  ///
  /// Throws [UseAfterReleaseError] if the object is previously released.
  ///
  /// Be careful when storing this in a variable since it might have gotten
  /// released upon use.
  NapiObjectPtr get pointer {
    if (isReleased) throw UseAfterReleaseError();
    return _finalizable.pointer;
  }

  /// Whether the underlying Napi reference is deleted or not.
  bool get isReleased;

  /// Whether the underlying Napi reference is `null` or not.
  bool get isNull;

  /// Deletes the underlying Napi reference and marks this as released.
  ///
  /// In HarmonyOS Napi, this uses napi_delete_reference instead of
  /// JNI's DeleteGlobalRef.
  ///
  /// Throws [DoubleReleaseError] if this is already released.
  ///
  /// Further uses of this object will throw [UseAfterReleaseError].
  void release() {
    _setAsReleased();
    _deleteReference();
  }

  void _deleteReference();

  void _setAsReleased();
}

/// A managed Napi global reference.
///
/// In HarmonyOS Napi, this uses napi_create_reference with NAPI_REF_NAPI
/// instead of JNI's NewGlobalRef/DeleteGlobalRef mechanism.
@pragma('vm:deeply-immutable')
final class ArkGlobalReference extends ArkReference {
  /// The Napi reference handle (similar to napi_ref in C)
  final ffi.Pointer<ffi.Void> _napiRefHandle;
  final ffi.Pointer<ffi.Bool> _isReleased;

  ArkGlobalReference._(
      super._finalizable, this._napiRefHandle, this._isReleased);

  factory ArkGlobalReference(ffi.Pointer<ffi.Void> pointer) {
    final finalizable = _ArkFinalizable(pointer);
    final isReleased = calloc<ffi.Bool>();
    
    // In HarmonyOS Napi, we would use napi_create_reference here
    // For now, we use a placeholder implementation
    final napiRefHandle = ffi.Pointer<ffi.Void>.fromAddress(0);
    
    return ArkGlobalReference._(
        finalizable, napiRefHandle, isReleased);
  }

  @override
  bool get isNull => pointer == ffi.Pointer.fromAddress(0);

  @override
  void _setAsReleased() {
    if (isReleased) {
      throw DoubleReleaseError();
    }
    _isReleased.value = true;
    // In HarmonyOS Napi, we would use napi_delete_reference here
    // For now, we just mark as released
  }

  @override
  void _deleteReference() {
    // In HarmonyOS Napi, this would call napi_delete_reference
    // instead of JNI's DeleteGlobalRef
    // For now, we do nothing as the reference is managed by Napi
  }

  @override
  bool get isReleased => _isReleased.value;
}

final ArkReference arkNullReference = _ArkNullReference();

@pragma('vm:deeply-immutable')
final class _ArkNullReference extends ArkReference {
  _ArkNullReference() : super(_ArkFinalizable(ffi.Pointer.fromAddress(0)));

  @override
  bool get isReleased => false;

  @override
  void _deleteReference() {
    // No need to delete `null`.
  }

  @override
  void _setAsReleased() {
    // No need to release `null`.
  }

  @override
  bool get isNull => true;
}


